
// === uuLayer ===
// depend: uuMeta, uuCanvas, uuStyle
/*

--- layer functions ---
uuLayer.getLayerInstance(elm) - return LayerObject or null

--- view operations ---
uuLayer.view
uuLayer.resizeView(width, height)
uuLayer.getViewInfo() - return { clid, cctx, front, rear,
                                 zmax, zmin, zorder, length }

--- layer operations ---
uuLayer.createLayer(id, type, hide = 0) - return new layer element
uuLayer.removeLayer(id) - return this
uuLayer.resizeLayer(id, width, height) - return this
uuLayer.refLayer(id) - return layer element
uuLayer.bringLayer(id, tgt) - return this
uuLayer.moveLayer(id = "", x, y, diff = false) - return this
uuLayer.showLayer(id = "") - return this
uuLayer.hideLayer(id = "") - return this
uuLayer.getLayerOpacity(id) - return 0.0 ~ 1.0
uuLayer.setLayerOpacity(id = "", opacity = 1.0, diff = false) - return this

  --- canvas 2D context operations ---
  uuLayer.getContext() - return current canvas context
  uuLayer.push(id) - return this
  uuLayer.pop() - return this

    --- canvas 2D context style operations ---
    uuLayer.alphas(globalAlpha) - return this
    uuLayer.fills(fillStyle) - return this
    uuLayer.wires(strokeStyle, lineWidth = undef) - return this
    uuLayer.fonts(font) - return this
    uuLayer.lines(lineWidth) - return this
    uuLayer.shadows(color = undef,
                    x = undef, y = undef, blur = undef) - return this
    uuLayer.sets(propHash) - return this
    uuLayer.gets(propHash) - return { prop: value, ... }

    --- canvas 2D context drawing operations ---
    uuLayer.clear(x = 0, y = 0,
                  w = canvas.width, h = canvas.height) - return this
    uuLayer.save() - return this
    uuLayer.restore() - return this
    uuLayer.scale(x, y) - return this
    uuLayer.translate(x, y) - return this
    uuLayer.rotate(90 or "90deg" or "1.2rad") - return this
    uuLayer.transform(m11, m12, m21, m22, dx, dy) - return this
    uuLayer.begin(x = undef, y = undef) - return this
    uuLayer.move(x, y) - return this
    uuLayer.line(x, y) - return this
    uuLayer.curve(a0, a1, a2, a3,
                  a4 = undef, a5 = undef) - return this
    uuLayer.clip() - return this
    uuLayer.arc(x, y, r, a0 = "0deg",
                         a1 = "360deg", clock = 1) - return this
    uuLayer.draw(wire = 0) - return this
    uuLayer.close() - return this
    uuLayer.text(text, x = 0, y = 0,
                 wire = 0, maxWidth = undef) - return this
    uuLayer.measureText(text) - return { width, height }
    uuLayer.poly([point, ...], wire = 0) - return this
    uuLayer.box(x, y, w, h, r = 0, wire = 0) - return this
    uuLayer.metabo(x, y, w, h, r = 0,
                   bulge = 10, wire = 0) - return this
    uuLayer.circle(x, y, w, h, r, wire = 0) - return this
    uuLayer.dots(x, y, w, h, {palette},
                 [data, ...], index = 0) - return this
    uuLayer.linearGrad(x1, y1,
                       x2, y2, [offset, ...],
                               [color, ...]) - return CanvasGradient
    uuLayer.radialGrad(x1, y1, r1,
                       x2, y2, r2, [offset, ...],
                                   [color, ...]) - return CanvasGradient
    uuLayer.pattern(image, pattern = "repeat") - return CanvasPattern
    uuLayer.image(image, arg1, arg2, arg3, arg4,
                         arg5, arg6, arg7, arg8) - return this

    --- canvas 2D context convenient operations ---

    uuLayer.fitImage(image) - return this
    uuLayer.grid(size = 10, unit = 5,
                 color = "skyblue", color2 = "steelblue") - return this
    uuLayer.angleGlossy(x, y, preset, extend) - return this
    uuLayer.metaboGlossy(x, y, preset, extend) - return this
    uuLayer.jellyBean(x, y, preset, extend) - return this
 */
(function() {
var _layer, // inner namespace
    _mm = uuMeta,
    _doc = document,
    _ie = _mm.ie,
    _opera = _mm.opera,
    _int = parseInt,
    _float = parseFloat,
    _math = Math,
    _rad = _math.PI / 180, // Math.toRadians
//  _deg = 180 / _math.PI, // Math.toDegrees
    _runstyle = _mm.runstyle,
    _mix = _mm.mix,
    _instance = {}, // { uid: layer }
    SHADOWS = { shadowColor:    1,
                shadowOffsetX:  2,
                shadowOffsetY:  3,
                shadowBlur:     4 };

_layer = function(view,     // @param Node: layer container
                  width,    // @param Number(= 300): view width
                  height) { // @param Number(= 150): view height
  var uid = _mm.uid(view);

  if (uid in _instance) {
    return _instance[uid]; // already
  }

  // uuLayer.view - public property
  this.view = view;

  this._layer = {}; // Hash( { id: elm, ctx, chain } )
  // for canvas context
  this._stack = [];    // context stack
  this._cctx = void 0; // current canvas context
  this._clid = void 0; // current canvas context layer id

  _mix(view.style, {
    width:  (width  || 300) + "px",
    height: (height || 150) + "px",
    zIndex: _int((_ie ? view[_runstyle]
                      : _runstyle(view, "")).zIndex) || 0,
    overflow: "hidden",
    position: "relative"
  });

  _instance[uid] = this;
};

// --- layer functions ---

// uuLayer.getLayerInstance
_layer.getLayerInstance = function(elm) { // @param Node:
                                          // @return LayerObject/null:
  var uid = _mm.uid(elm);
  if (uid in _instance) {
    return _instance[uid];
  }
  return null;
};


_layer.prototype = {

// --- view operations ---

  // uuLayer.resizeView - resize view
  resizeView: function(width,    // @param Number:
                       height) { // @param Number:
    this.view.style.width  = width;
    this.view.style.height = height;
  },

  // uuLayer.getViewInfo - get view state
  getViewInfo: function() { // @return Hash: { clid, cctx, front, rear,
                            //                 zmax, zmin, zorder, length }
    var hash = this._layer, v, i, j = 0,
        front, rear, max = 0, min = 0, order = [];

    for (i in hash) {
      ++j;
      v = _int(hash[i].elm.style.zIndex);
      (!front || max <= v) && (max = v, front = i);
      (!rear  || min >= v) && (min = v, rear = i);
      order[v] = i;
    }
    return { clid:  this._clid,   // current layer id
             cctx:  this._cctx,   // current canvas context
             front: front || "",  // front layer id
             rear:  rear  || "",  // rear layer id
             zmax:  max || 0,     // front layer z-index
             zmin:  min || 0,     // rear layer z-index
             zorder: order,       // order["a", "b", "c"]
             length: j };         // layer.length
  },

// --- layer operations ---

  // uuLayer.createLayer - create child layer
  createLayer:
      function(id,      // @param String: layer id
               type,    // @param String: "canvas", "vmlcanvas",
                        //                "div", "img", etc...
               hide) {  // @param Boolean(= false): true = hidden layer
                        // @return Node: new layer element
    type = type.toLowerCase();

    var elm, ctx, v = this.view;

    if (/canvas$/.test(type)) {
      elm = v.appendChild(uuCanvas.init(_doc.createElement("canvas"),
                                        type === "vmlcanvas"));
      elm.width  = _int(v.style.width);
      elm.height = _int(v.style.height);
      ctx = elm.getContext("2d");
      ctx.textBaseline = "top"; // force
      // set current context
      this._clid = id;
      this._cctx = ctx;
    } else {
      elm = v.appendChild(_doc.createElement(type));
      if (type !== "img") {
        elm.style.width  = v.style.width;
        elm.style.height = v.style.height;
      }
      v.appendChild(elm);
    }
    _mix(elm.style, {
      top: "0px",
      left: "0px",
      zIndex: this.getViewInfo().length,
      display: hide ? "none": "",
      position: "absolute"
    });
    this._layer[id] = { elm: elm, ctx: ctx, chain: [] };
    return elm;
  },

  // uuLayer.removeLayer - remove child layer
  removeLayer: function(id) { // @param String: layer id
                              // @return this:
    if (id in this._layer) {
      var v = this._layer[id], i = 0, iz = v.chain.length;
      for (; i < iz; ++i) {
        v.chain[i].elm.parentNode.removeChild(v.chain[i].elm);
        delete this._layer[v.chain[i]];
      }
      v.elm.parentNode.removeChild(v.elm);
      v.elm = void 0;
      delete this._layer[id];

      // reset context stack
      this._stack = [];
      this._clid = void 0;
      this._cctx = void 0;
    }
    return this;
  },

  // uuLayer.resizeLayer - resize child layer
  resizeLayer: function(id,       // @param String: layer id
                        width,    // @param Number: pixel width
                        height) { // @param Number: pixel height
                                  // @return this:
    var node = this._layer[id].elm;

    switch (node.tagName.toLowerCase()) {
    case "canvas":
    case "img":
      node.width  = width;
      node.height = height;
      break;
    default:
      node.style.width  = width  + "px";
      node.style.height = height + "px";
    }
    return this;
  },

  // uuLayer.refLayer - refer child layer
  refLayer: function(id) { // @param String: layer id
                           // @return Node: layer element
    return this._layer[id].elm;
  },

  // uuLayer.bringLayer - lift child layer
  //    bringLayer("a") is bring to front
  //    bringLayer("a", "b") is bring to layer("b")
  bringLayer: function(id,    // @param String: move layer id
                       tgt) { // @param String(= ""): target layer id
                              // @return this:
    var ly = this._layer, z, i,
        p1 = _int(ly[id].elm.style.zIndex),
        p2 = _int(ly[tgt || this.getViewInfo().front].elm.style.zIndex);

    if (p1 < p2) {
      for (i in ly) {
        z = _int(ly[i].elm.style.zIndex);
        if (z > p1 && z <= p2) {
          ly[i].elm.style.zIndex = z - 1;
        }
      }
      ly[id].elm.style.zIndex = p2;
    } else if (p1 === p2) {
      ly[id].elm.style.zIndex = p1 + 1;
    }
    return this;
  },

  _chains: function(id) {
    var rv = {}, v, i = 0, iz;

    rv[id] = v = this._layer[id];
    for (iz = v.chain.length; i < iz; ++i) {
      rv[v.chain[i]] = this._layer[v.chain[i]];
    }
    return rv;
  },

  // uuLayer.moveLayer - set absolute/relative child layer position
  //    moveLayer("", 100, 100) is move all layers to pos(100px, 100px)
  //    moveLayer("a", 100, 100, 1) is move layer("a") to pos(+100px, +100px)
  moveLayer:
      function(id,     // @param String(= ""): layer id
               x,      // @param Number: style.left value(unit px)
               y,      // @param Number: style.top value(unit px)
               diff) { // @param Boolean(= false): difference,
                       //           false = x and y is absolute value
                       //           true = x and y is relative value
                       // @return this:
    x = _int(x), y = _int(y), diff = diff || 0;

    var hash = id ? this._chains(id) : this._layer, v, i,
        doPixel = _ie || _opera;

    for (i in hash) {
      v = hash[i].elm.style;
      if (doPixel) {
        v.pixelLeft = (diff ? v.pixelLeft : 0) + x;
        v.pixelTop  = (diff ? v.pixelTop  : 0) + y;
      } else {
        v.left = (diff ? _int(v.left) : 0) + x + "px";
        v.top  = (diff ? _int(v.top)  : 0) + y + "px";
      }
    }
    return this;
  },

  // uuLayer.showLayer - show child layer
  //    showLayer() is show all layers
  //    showLayer("a") is show layer("a")
  showLayer: function(id) { // @param String(= ""): layer id
                            // @return this:
    return this._showLayer(id, 0);
  },

  // uuLayer.hideLayer - hide child layer
  //    hideLayer() is hide all layers
  //    hideLayer("a") is hide layer("a")
  hideLayer: function(id) { // @param String(= ""): layer id
                            // @return this:
    return this._showLayer(id, 1);
  },

  _showLayer: function(id, hide) {
    var hash = id ? this._chains(id) : this._layer, i;

    for (i in hash) {
      hash[i].elm.style.display = hide ? "none" : "block";
    }
    return this;
  },

  // uuLayer.getLayerOpacity - get child layer opacity value(from 0.0 to 1.0)
  getLayerOpacity:
      function(id) { // @param String: layer id
                     // @return Number: float value(min: 0.0, max: 1.0)
    return uuStyle.getOpacity(this._layer[id].elm);
  },

  // uuLayer.setLayerOpacity - set child layer opacity value(from 0.0 to 1.0)
  //    setLayerOpacity("", 0.1, 1) is set all layers opacity(+0.1)
  //    setLayerOpacity("a", 0.5) is set layer("a") opacity(0.5)
  setLayerOpacity:
      function(id,      // @param String(= ""): layer id
               opacity, // @param Number(= 1.0): float value(0.0 to 1.0)
               diff) {  // @param Boolean(= false):
                        // @return this:
    opacity = _float(opacity === void 0 ? 1.0 : opacity);
    diff = diff || 0;

    var hash = id ? this._chains(id) : this._layer, i;

    for (i in hash) {
      uuStyle.setOpacity(hash[i].elm, opacity, diff);
    }
    return this;
  },

// --- canvas 2D context operations ---

  // uuLayer.getContext - get current canvas context
  getContext: function() { // @return CanvasRenderingContext2D:
    return this._cctx;
  },

  // uuLayer.push - push current context
  push: function(id) { // @param String: layer id
                       // @return this:
    this._clid && this._stack.push(this._clid);
    this._clid = id;
    this._cctx = this._layer[id].ctx;
    return this;
  },

  // uuLayer.pop - pop current context
  pop: function() { // @param String: layer id
                    // @return this:
    if (this._stack.length) {
      this._clid = this._stack.pop();
      this._cctx = this._layer[this._clid].ctx;
    }
    return this;
  },

// --- canvas 2D context style operations ---

  // uuLayer.alphas - set globalAlpha
  //    globalAlpha: from 0.0 to 1.0
  alphas: function(globalAlpha) { // @param Number: globalAlpha
                                  // @return this:
    this._cctx.globalAlpha = globalAlpha;
    return this;
  },

  // uuLayer.fills - set fillStyle
  fills: function(fillStyle) { // @param String/Object: fillStyle
                               // @return this:
    this._cctx.fillStyle = fillStyle;
    return this;
  },

  // uuLayer.wires - set strokeStyle
  wires:
      function(strokeStyle, // @param String/Object: strokeStyle
               lineWidth) { // @param Number(= undefined): lineWidth(from 1.0)
                            // @return this:
    this._cctx.strokeStyle = strokeStyle;
    if (lineWidth !== void 0) {
      this._cctx.lineWidth = lineWidth;
    }
    return this;
  },

  // uuLayer.fonts - set font style
  //    font: CSS font style value. (eg: "10px sans-serif")
  fonts: function(font) { // @param CSSFontString: font
                          // @return this:
    this._cctx.font = font;
    return this;
  },

  // uuLayer.lines - set lineWidth
  lines: function(lineWidth) { // @param Number: lineWidth(from 1.0)
                               // @return this:
    this._cctx.lineWidth = lineWidth;
    return this;
  },

  // uuLayer.setShadow - set shadow styles
  shadows: function(color,  // @param String(= undefined): shadowColor
                    x,      // @param Number(= undefined): shadowOffsetX
                    y,      // @param Number(= undefined): shadowOffsetY
                    blur) { // @param Number(= undefined): shadowBlur
                            // @return this:
    var ctx = this._cctx;
    (color !== void 0) && (ctx.shadowColor   = ctx._shadow[0] = color);
    (x     !== void 0) && (ctx.shadowOffsetX = ctx._shadow[1] = x);
    (y     !== void 0) && (ctx.shadowOffsetY = ctx._shadow[2] = y);
    (blur  !== void 0) && (ctx.shadowBlur    = ctx._shadow[3] = blur);
    return this;
  },

  // uuLayer.sets - set styles
  sets: function(propHash) { // @param Hash: { prop: value, ... }
                             // @return this:
    var ctx = this._cctx, i, v = 0;
    for (i in propHash) {
      if ( (v = SHADOWS[i]) ) {
        ctx._shadow[v - 1] = propHash[i];
      }
      ctx[i] = propHash[i];
    }
    return this;
  },

  // uuLayer.gets - get styles
  gets: function(propHash) { // @param Hash: { font: "", ... }
                             // @return Hash: { font: "32pt Arial", ... }
    var ctx = this._cctx, i, rv = {}, v = 0;
    for (i in propHash) {
      if ( (v = SHADOWS[i]) ) {
        rv[i] = ctx[i] || ctx._shadow[v - 1];
      } else {
        rv[i] = ctx[i];
      }
    }
    return rv;
  },

// --- canvas 2D context drawing operations ---

  // uuLayer.clear - clear rect
  clear: function(x,   // @param Number(= 0): position x
                  y,   // @param Number(= 0): position y
                  w,   // @param Number(= canvas.width):  width
                  h) { // @param Number(= canvas.height): height
                       // @return this:
    var ctx = this._cctx;
    ctx.clearRect(x || 0, y || 0,
                  w || ctx.canvas.width, h || ctx.canvas.height);
    return this;
  },

  // uuLayer.save
  save: function() { // @return this:
    this._cctx.save();
    return this;
  },

  // uuLayer.restore
  restore: function() { // @return this:
    this._cctx.restore();
    return this;
  },

  // uuLayer.scale - scale
  scale: function(w,   // @param Number: width scale
                  h) { // @param Number: height scale
                       // @return this:
    this._cctx.scale(w, h);
    return this;
  },

  // uuLayer.translate - offset origin
  translate: function(x,   // @param Number: offset x
                      y) { // @param Number: offset y
                           // @return this:
    this._cctx.translate(x, y);
    return this;
  },

  // uuLayer.rotate - rotate
  //    angle: 360 or "360deg" or "1.5rad"
  rotate: function(angle) { // @param Number/String: angle
                            // @return this:
    var ang = (/rad$/.test(angle + "") ? 1 : _rad) * _float(angle);
    this._cctx.rotate(ang);
    return this;
  },

  // uuLayer.transform
  transform: function(m11, m12, m21, m22, dx, dy) {
    this._cctx.transform(m11, m12, m21, m22, dx, dy);
    return this;
  },

  // uuLayer.begin - beginPath + moveTo
  begin: function(x,    // @param Number(= undefined): move x
                  y) {  // @param Number(= undefined): move y
                        // @return this:
    this._cctx.beginPath();
    (x !== void 0 && y !== void 0) && this._cctx.moveTo(x || 0, y || 0);
    return this;
  },

  // uuLayer.move - moveTo
  move: function(x,   // @param Number: move x
                 y) { // @param Number: move y
                      // @return this:
    this._cctx.moveTo(x, y);
    return this;
  },

  // uuLayer.line - lineTo
  line: function(x,   // @param Number: move x
                 y) { // @param Number: move y
                      // @return this:
    this._cctx.lineTo(x, y);
    return this;
  },

  // uuLayer.curve - quadraticCurveTo or bezierCurveTo
  curve: function(a0,   // @param Number:
                  a1,   // @param Number:
                  a2,   // @param Number:
                  a3,   // @param Number:
                  a4,   // @param Number(= undefined):
                  a5) { // @param Number(= undefined):
                        // @return this:
    if (a4 === void 0) {
      // cpx, cpy, x, y
      this._cctx.quadraticCurveTo(a0, a1, a2, a3);
    } else {
      // cp1x, cp1y, cp2x, cp2y, x, y
      this._cctx.bezierCurveTo(a0, a1, a2, a3, a4, a5);
    }
    return this;
  },

  // uuLayer.clip - clip
  clip: function() { // @return this:
    this._cctx.clip();
    return this;
  },

  // uuLayer.arc - arc
  //    a0 and a1: 360 or "360deg" or "0rad"
  arc: function(x,       // @param Number:
                y,       // @param Number:
                r,       // @param Number:
                a0,      // @param Number/String(= "0deg"): angle0
                a1,      // @param Number/String(= "360deg"): angle1
                clock) { // @param Boolean(= true):
                         // @return this:
    a0 = a0 || "0deg";
    a1 = a1 || "360deg";
    var ang0 = (/rad$/.test(a0 + "") ? 1 : _rad) * _float(a0),
        ang1 = (/rad$/.test(a1 + "") ? 1 : _rad) * _float(a1);
    this._cctx.arc(x, y, r, ang0, ang1,
                   (clock === void 0) ? 0 : !clock);
    return this;
  },

  // uuLayer.draw - fill or stroke
  draw: function(wire) { // @param Boolean(= false):
                         // @return this:
    wire ? this._cctx.stroke() : this._cctx.fill();
    return this;
  },

  // uuLayer.close - closePath
  close: function() { // @return this:
    this._cctx.closePath();
    return this;
  },

  // uuLayer.text
  text: function(text,       // @param String:
                 x,          // @param Number(= 0):
                 y,          // @param Number(= 0):
                 wire,       // @param Boolean(= false):
                 maxWidth) { // @param Number(= undefined):
                             // @return this:
    var fn = wire ? "strokeText" : "fillText";
    if (maxWidth === void 0) { // for Firefox3.5 bug
      this._cctx[fn](text, x || 0, y || 0);
    } else {
      this._cctx[fn](text, x || 0, y || 0, maxWidth);
    }
    return this;
  },

  // uuLayer.measureText - get text dimension
  measureText: function(text) { // @param String:
                                // @return TextMetrics: { width, height }
    this._cctx.measureText(text);
  },

  // uuLayer.poly - poly line + fill
  poly: function(point,  // @param PointArray: Array( [x0, y0, x1, y1, ... ] )
                 wire) { // @param Boolean(= false):
                         // @return this:
    var p = point || [0, 0], i, iz = point.length;
    this.close().begin(p[0], p[1]);
    for (i = 2; i < iz; i += 2) {
      this.line(p[i], p[i + 1]);
    }
    this.draw(wire).close();
  },

  // uuLayer.box - add box path, fill inside
  box: function(x,      // @param Number:
                y,      // @param Number:
                w,      // @param Number:
                h,      // @param Number:
                r,      // @param Number(= 0):
                wire) { // @param Boolean(= false):
                        // @return this:
    if (!r) {
      wire ? this._cctx.strokeRect(x, y, w, h)
           : this._cctx.fillRect(x, y, w, h);
      return this;
    }
    // round corner
    return this.close().begin(x, y + r).line(x, y + h - r).
                curve(x, y + h, x + r, y + h).line(x + w - r, y + h).
                curve(x + w, y + h, x + w, y + h - r).line(x + w, y + r).
                curve(x + w, y, x + w - r, y).line(x + r, y).
                curve(x, y, x, y + r).draw(wire).
                close();
  },

  // uuLayer.metabolic - metabolic box
  metabo: function(x,      // @param Number:
                   y,      // @param Number:
                   w,      // @param Number:
                   h,      // @param Number:
                   r,      // @param Number(= 0):
                   bulge,  // @param Number(= 10):
                   wire) { // @param Boolean(= false):
                           // @return this:
    r = r || 0;
    bulge = (bulge === void 0) ? 10 : bulge;

    if (bulge) {
      return this.close().begin(x, y + r).line(x, y + h - r). // 1
                  curve(x + w * 0.5, y + h + bulge, x + w, y + h - r). // 2,3,4
                  line(x + w, y + r). // 5
                  curve(x + w, y, x + w - r, y).line(x + r, y). // 6,7
                  curve(x, y, x, y + r).draw(wire). // 8
                  close();
    }
    return this.close().begin(x, y + r).line(x, y + h). // 1
                line(x + w, y + h). // 2,3,4
                line(x + w, y + r). // 5
                curve(x + w, y, x + w - r, y).line(x + r, y). // 6,7
                curve(x, y, x, y + r).draw(wire). // 8
                close();
  },

  // uuLayer.circle - circle + fill
  circle: function(x,      // @param Number:
                   y,      // @param Number:
                   w,      // @param Number:
                   h,      // @param Number:
                   r,      // @param Number:
                   wire) { // @param Boolean(= false):
                           // @return this:
    if (w === h) { // circle
      return this.close().begin(x, y).arc(x, y, r).
                  draw(wire).close();
    }
    // ellipse(oval) not impl.
    return this;
  },

  // uuLayer.dots - draw dot with palette
  //    palette: Hash( { paletteNo: "#ffffff" or { r,g,b,a }, ...} )
  //    data: [paletteNo, paletteNo, ...]
  dots: function(x,       // @param Number:
                 y,       // @param Number:
                 w,       // @param Number:
                 h,       // @param Number:
                 palette, // @param Hash: color palette,
                 data,    // @param Array: dot data
                 index) { // @param Number(= 0): start data index
                          // @return this:
    var ctx = this._cctx, i = 0, j = 0, p, v, idx = index || 0;
    for (; j < h; ++j) {
      for (i = 0; i < w; ++i) {
        v = data[idx + i + j * w];
        if (!(v in palette)) {
          continue;
        }
        p = palette[v];
        if (typeof p === "string") {
          ctx.fillStyle = p;
        } else if (p.a) { // skip alpha = 0
          ctx.fillStyle = "rgba(" + [p.r, p.g, p.b, p.a].join(",") + ")";
        }
        ctx.fillRect(x + i, y + j, 1, 1);
      }
    }
    return this;
  },

  // uuLayer.linearGrad - create linear gradient
  linearGrad:
      function(x1,      // @param Number:
               y1,      // @param Number:
               x2,      // @param Number:
               y2,      // @param Number:
               offset,  // @param Array: [offset, ...], offset from 0.0 to 1.0
               color) { // @param Number: [color, ...]
                        // @return CanvasGradient:
    var rv = this._cctx.createLinearGradient(x1, y1, x2, y2),
        i = 0, iz = offset.length;

    for (; i < iz; ++i) {
      rv.addColorStop(offset[i], color[i]);
    }
    return rv; // CanvasGradient object
  },

  // uuLayer.radialGrad - create radial gradient
  radialGrad:
      function(x1,      // @param Number:
               y1,      // @param Number:
               r1,      // @param Number:
               x2,      // @param Number:
               y2,      // @param Number:
               r2,      // @param Number:
               offset,  // @param Array: [offset, ...], offset from 0.0 to 1.0
               color) { // @param Number: [color, ...]
                        // @return CanvasGradient:
    var rv = this._cctx.createRadialGradient(x1, y1, r1, x2, y2, r2),
        i = 0, iz = offset.length;

    for (; i < iz; ++i) {
      rv.addColorStop(offset[i], color[i]);
    }
    return rv; // CanvasGradient object
  },

  // uuLayer.pattern - create pattern
  pattern: function(image,     // @param HTMLImageElement
                               //        /HTMLCanvasElement:
                    pattern) { // @param String(= "repeat"):
                               // @return CanvasPattern:
    return this._cctx.createPattern(image, pattern || "repeat");
  },

  // uuLayer.image - image
  image: function(image,  // @param HTMLImageElement
                          //        /HTMLCanvasElement:
                  arg1,   // @param Number(= undefined):
                  arg2,   // @param Number(= undefined):
                  arg3,   // @param Number(= undefined):
                  arg4,   // @param Number(= undefined):
                  arg5,   // @param Number(= undefined):
                  arg6,   // @param Number(= undefined):
                  arg7,   // @param Number(= undefined):
                  arg8) { // @param Number(= undefined):
                          // @return this:
    switch (arguments.length) {
    case 1: this._cctx.drawImage(image, 0, 0); break;
    case 3: this._cctx.drawImage(image, arg1, arg2); break;
    case 5: this._cctx.drawImage(image, arg1, arg2, arg3, arg4); break;
    case 9: this._cctx.drawImage(image, arg1, arg2, arg3, arg4,
                                        arg5, arg6, arg7, arg8); break;
    default: throw "";
    }
    return this;
  },

// --- canvas 2D context convenient operations ---

  // uuLayer.fitImage - image fitting(auto-scaling)
  fitImage: function(image) { // @param HTMLImageElement: image element
                              // @return this:
    var ctx = this._cctx,
        dim = uuStyle.getActualDimension(image),
        cw = _int(ctx.canvas.width),
        ch = _int(ctx.canvas.height),
        sw = dim.w,
        sh = dim.h,
        dx = (sw <= cw) ? _math.floor((cw - sw) / 2) : 0,
        dy = (sh <= ch) ? _math.floor((ch - sh) / 2) : 0,
        dw = (sw <= cw) ? sw : cw,
        dh = (sh <= ch) ? sh : ch;
    ctx.drawImage(image, 0, 0, sw, sh, dx, dy, dw, dh);
    return this;
  },

  // uuLayer.grid - draw hatch
  grid: function(size,     // @param Number(= 10):
                 unit,     // @param Number(= 5):
                 color,    // @param String(= "skyblue"):
                 color2) { // @param String(= "steelblue"):
                           // @return this:
    size = size || 10, unit = unit || 5;
    color = color || "skyblue", color2 = color2 || "steelblue";
    var x = size, y = size, i = 1, j = 1,
        w = _int(this._cctx.canvas.width),
        h = _int(this._cctx.canvas.height);

    for (; x < w; ++i, x += size) {
      this.wires((i % unit) ? color : color2).
           begin(x, 0).line(x, h).draw(1).close();
    }
    for (; y < h; ++j, y += size) {
      this.wires((j % unit) ? color : color2).
           begin(0, y).line(w, y).draw(1).close();
    }
    return this;
  },

  // uuLayer.angleGlossy
  //    override: Hash ( { gcolor1, gcolor2, overlayAlpha, w, h, r, angle } )
  angleGlossy: function(x,        // @param Number: move x
                        y,        // @param Number: move y
                        preset,   // @param String(= "GBLACK"): preset name
                        extend) { // @param Hash(= undefined): extend style
                                  // @return this:
    preset = (preset || "GBLACK").toUpperCase();
    preset = (preset in this.preset) ? this.preset[preset] : {};
    extend = _mix({ w: 100, h: 100, r: 12, angle: 0 }, preset, extend || {});

    var w = extend.w, h = extend.h, r = extend.r, angle = extend.angle,
        oa = extend.overlayAlpha, b = 3, dist = 0; // bevel size

    if (angle < -45) { angle = -45; }
    if (angle >  45) { angle =  45; }

    this.fills(this.linearGrad(x, y, x, y + h,
                               [0.0, 1.0], [extend.gcolor1, extend.gcolor2])).
         begin().box(x, y, w, h, r).close().
         fills("rgba(255,255,255," + oa + ")");

    switch (angle) {
    case 45:  this.begin(x + b, y + b + r).line(x + b, y + h - b * 2).
                    line(x + w - b * 2, y + b).line(x + b + r, y + b).
                    curve(x, y, x + b, y + b + r).draw().close(); break;
    case -45: this.begin(x - b + w, y + b + r).line(x - b + w, y + h - b * 2).
                    line(x + b * 2, y + b).line(x - b - r + w, y + b).
                    curve(x + w, y, x - b + w, y + b + r).draw().close(); break;
    default:  dist = ((h - b * 2) / 45 * angle) / 2;
              this.begin(x + b, y + b + r).
                    line(x + b, y + (h / 2) - b * 2 + dist).
                    line(x + w - b, y + (h / 2) - b * 2 - dist).
                    line(x + w - b, y + b + r).
                    curve(x + w, y, x + w - r, y + b).line(x + b + r, y + b).
                    curve(x, y, x + b, y + b + r).draw().close();
    }
    return this;
  },

  // uuLayer.metaboGlossy
  metaboGlossy: function(x,        // @param Number: move x
                         y,        // @param Number: move y
                         preset,   // @param String(= "GBLACK"): preset name
                         extend) { // @param Hash(= undefined): extend style
                                   // @return this:
    preset = (preset || "GBLACK").toUpperCase();
    preset = (preset in this.preset) ? this.preset[preset] : {};
    extend = _mix({ w: 100, h: 50, r: 12, bulge: 6 }, preset, extend || {});

    var w = extend.w, h = extend.h, r = extend.r, bulge = extend.bulge,
        oa = extend.overlayAlpha, r2 = r > 4 ? r - 4 : 0, b = 3; // bevel size

    this.fills(this.linearGrad(x, y, x, y + h,
                               [0.0, 1.0], [extend.gcolor1, extend.gcolor2])).
          begin().box(x, y, w, h, r).close().
          fills("rgba(255,255,255," + oa + ")").
          begin().metabo(x + b, y + b, w - b * 2, h * 0.5, r2, bulge).close();
    return this;
  },

  // uuLayer.jellyBean
  jellyBean: function(x,        // @param Number: move x
                      y,        // @param Number: move y
                      preset,   // @param String(= "GBLACK"): preset name
                      extend) { // @param Hash(= undefined): extend style
                                // @return this:
    extend = _mix({ w: 100, h: 30, r: 16, bulge: 6 }, extend || {});
    this.metaboGlossy(x, y, preset, extend);
    return this;
  },

// --- ---
  createReflectionLayer:
      function(id,             // @param String: layer id
               image,          // @param Node: image element
               hide,           // @param Boolean(= false): true = hidden layer
               x,              // @param Number(= 0): x
               y,              // @param Number(= 0): y
               width,          // @param Number(= image width): width
               height,         // @param Number(= image height): height
               mirrorHeight) { // @param Number(= 0.625): mirror height(0 to 1)
                               // @return Node: new layer element
    var dim = uuStyle.getActualDimension(image),
        w = width || dim.w,
        h = height || dim.h,
        mh = (mirrorHeight === void 0) ? 0.625 : mirrorHeight;

    if (!_ie) {
      return this._addReflection(id, image, hide,
                                 dim, x || 0, y || 0, w, h, mh);
    }
    return this._addReflectionIE(id, image, hide,
                                 dim, x || 0, y || 0, w, h, mh);
  },

  _addReflection: function(id, image, hide, dim, x, y, w, h, mh) {
    var elm = this.createLayer(id, "canvas", hide),
        grad,
        sx = w / dim.w, // scale x
        sy = h / dim.h; // scale y

    elm.width  = w;
    elm.height = h + h * mh;

    this.moveLayer(id, x, y);

    x = 0, y = 0;
    grad = this.linearGrad(x, y + h, x, y + h + h * mh,
                           [mh, 0],
                           ["rgba(255, 255, 255, 1.0)",
                            "rgba(255, 255, 255, 0.3)"]),
    this.clear().
      save().translate(x, y).scale(sx, sy).image(image).restore().
      save().translate(x, y + h * 2).scale(sx, -sy).image(image).restore().
      save().sets({ globalCompositeOperation: "destination-out" }).
             fills(grad).box(x, y + h, w, h * mh).restore();
    return elm;
  },

  _addReflectionIE: function(id, image, hide, dim, x, y, w, h, mh) {
    var img1 = this.createLayer(id, "img", hide),
        img2 = this.createLayer(id + "_reflection", "img", hide); // mirror

    _mix(img1, { src: image.src, width: w, height: h });
    _mix(img2, { src: image.src, width: w, height: h });

    _mix(img1.style, { top: x + "px", left: y + "px" });
    _mix(img2.style, { top: x + h + "px", left: y + "px" });
    img2.style.filter =
        "flipv progid:DXImageTransform.Microsoft.Alpha" +
        "(opacity=70,style=1,finishOpacity=0," +
        "startx=0,starty=0,finishx=0,finishy=" + ((1 - mh) * 100) + ")";

    this._layer[id].chain.push(id + "_reflection");
    return img1;
  }
};

// --- initialize ---
(function() {
  function make(gcolo1, gcolor2, overlayAlpha) {
    return { gcolor1: gcolo1, gcolor2: gcolor2, overlayAlpha: overlayAlpha };
  }
  _layer.prototype.preset = {
    GBLACK:   make("#000",    "#333",    0.25),
    GGRAY:    make("black",   "silver",  0.38),
    GSLIVER:  make("gray",    "white",   0.38),
    GBLUE:    make("#0000a0", "#0097ff", 0.38),
    GGREEN:   make("#006400", "#00ff00", 0.38),
    GRED:     make("#400000", "#ff0000", 0.38),
    GLEMON:   make("#dfcc00", "#FFE900", 0.38),
    GGOLD:    make("#fffacd", "gold",    0.45), // lemonchiffon
    GPEACH:   make("violet",  "red",     0.38),
    GBLOODORANGE:
              make("orange",  "red",     0.38)
  };
})();

// --- export ---
window.uuLayer = _layer; // window.uuLayer

})(); // uuLayer scope
